﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Collections;

namespace System.Collections.Observable.Transaction
{
    static internal class IListExtensions
    {
        public static void AddRange(this IList target, IList range)
        {
            foreach (Object o in range)
            {
                target.Add(o);
            }
        }

        public static void InsertRange(this IList target, int index, IList range)
        {
            for (int i = 0; i < range.Count; i++)
            {
                target.Insert(index + i, range[i]);
            }
        }
    }

    class PendingCollectionChangedEvent
    {
        NotifyCollectionChangedEventArgs OriginalEventArgs;

        NotifyCollectionChangedAction m_action;
        NotifyCollectionChangedAction Action
        {
            get
            {
                if (OriginalEventArgs != null)
                {
                    return OriginalEventArgs.Action;
                }

                return m_action;
            }
        }

        IList m_newItems;
        IList NewItems
        {
            get
            {
                if (OriginalEventArgs != null)
                {
                    return OriginalEventArgs.NewItems;
                }
                return m_newItems;
            }
        }

        IList m_oldItems;
        IList OldItems
        {
            get
            {
                if (OriginalEventArgs != null)
                {
                    return OriginalEventArgs.OldItems;
                }

                return m_oldItems;
            }
        }

        int m_newStartingIndex;
        int NewStartingIndex
        {
            get
            {
                if (OriginalEventArgs != null)
                {
                    return OriginalEventArgs.NewStartingIndex;
                }
                return m_newStartingIndex;
            }
        }

        int m_oldStartingIndex;
        int OldStartingIndex
        {
            get
            {
                if (OriginalEventArgs != null)
                {
                    return OriginalEventArgs.OldStartingIndex;
                }

                return m_oldStartingIndex;
            }
        }

        void DiscardOriginalEventArgs()
        {
            if (OriginalEventArgs == null) return;

            m_action = OriginalEventArgs.Action;

            m_newItems = null;
            if (OriginalEventArgs.NewItems != null)
            {
                m_newItems = new ArrayList(OriginalEventArgs.NewItems);
            }

            m_oldItems = null;
            if (OriginalEventArgs.OldItems != null)
            {
                m_oldItems = new ArrayList(OriginalEventArgs.OldItems);
            }

            m_newStartingIndex = OriginalEventArgs.NewStartingIndex;
            m_oldStartingIndex = OriginalEventArgs.OldStartingIndex;

            OriginalEventArgs = null;
        }

        public NotifyCollectionChangedEventArgs EventArgs
        {
            get
            {
                if (OriginalEventArgs != null)
                {
                    return OriginalEventArgs;
                }
                else switch (Action)
                {
                    case NotifyCollectionChangedAction.Add:
                        return new NotifyCollectionChangedEventArgs(Action, NewItems, NewStartingIndex);                            

                    case NotifyCollectionChangedAction.Remove:
                        return new NotifyCollectionChangedEventArgs(Action, OldItems, OldStartingIndex);                            

                    case NotifyCollectionChangedAction.Replace:
                        return new NotifyCollectionChangedEventArgs(Action, OldItems, NewItems, NewStartingIndex);                            

                    case NotifyCollectionChangedAction.Reset:
                        return new NotifyCollectionChangedEventArgs(Action);                            

                    case NotifyCollectionChangedAction.Move:
                        return new NotifyCollectionChangedEventArgs(Action, NewItems, NewStartingIndex, OldStartingIndex);                            

                    default:
                        throw new InvalidProgramException("Unknown Action.");
                }
                
            }
        }

        public PendingCollectionChangedEvent(NotifyCollectionChangedEventArgs args)
        {
            OriginalEventArgs = args;            
        }
        

        public void Merge(NotifyCollectionChangedEventArgs pendingEvent)
        {
            if (!TryMerge(pendingEvent))
            {
                throw new ArgumentException("Can't merge specified event.");
            }
        }

        public bool TryMerge(NotifyCollectionChangedEventArgs pendingEvent)
        {
            //We can't merge objects that are subclasses of NotifyCollectionChangedEventArgs.            
            if (pendingEvent.GetType() != typeof(NotifyCollectionChangedEventArgs))
            {
                return false;
            }

            //can merge only Add and Remove events
            if (Action == NotifyCollectionChangedAction.Replace ||
                Action == NotifyCollectionChangedAction.Move ||
                Action == NotifyCollectionChangedAction.Reset)
            {
                return false;
            }

            //both actions must be the same type to be mergable
            if (pendingEvent.Action != Action)
            {
                return false;
            }

            if (Action == NotifyCollectionChangedAction.Add)
            {
                return TryMergeAdd(pendingEvent);
            }

            if (Action == NotifyCollectionChangedAction.Remove)
            {
                return TryMergeRemove(pendingEvent);
            }            

            //all other combinations can not be merged
            return false;
        }

        private bool TryMergeAdd(NotifyCollectionChangedEventArgs pendingEvent)
        {
            //we support only Add event at this functions
            if (Action != NotifyCollectionChangedAction.Add ||
                pendingEvent.Action != NotifyCollectionChangedAction.Add)
            {
                return false;
            }

            //new items must not be null
            if (NewItems == null || pendingEvent.NewItems == null) return false;
            
            //OldStartungIndex must be -1 for both
            if (OldStartingIndex != -1 || pendingEvent.OldStartingIndex != -1) return false;

            //both indecies must be either -1 or a non-negatives
            if (NewStartingIndex * pendingEvent.NewStartingIndex < 0)
            {
                return false;
            }

            //if both events are index unaware
            if (NewStartingIndex < 0 && pendingEvent.NewStartingIndex < 0)
            {
                DiscardOriginalEventArgs();
                NewItems.AddRange(pendingEvent.NewItems);
                return true;
            }

            //if new item(s) should be inserted just before current items
            if (NewStartingIndex == pendingEvent.NewStartingIndex)
            {
                DiscardOriginalEventArgs();
                NewItems.InsertRange(0, pendingEvent.NewItems);
                return true;
            }

            //if new item(s) should be inserted after current
            if (NewStartingIndex + NewItems.Count == pendingEvent.NewStartingIndex)
            {
                DiscardOriginalEventArgs();
                NewItems.AddRange(pendingEvent.NewItems);
                return true;
            }

            //all other combinations can not be merged
            return false;
        }

        private bool TryMergeRemove(NotifyCollectionChangedEventArgs pendingEvent)
        {
            if (Action != NotifyCollectionChangedAction.Remove ||
                pendingEvent.Action != NotifyCollectionChangedAction.Remove)
            {
                return false;
            }

            //new items must not be null
            if (OldItems == null || pendingEvent.OldItems == null) return false;

            //NewStartingIndex must be -1 for both
            if (NewStartingIndex != -1 || pendingEvent.NewStartingIndex != -1) return false;

            //both old indecies must be either -1 or a non-negatives
            if (OldStartingIndex * pendingEvent.OldStartingIndex < 0)
            {
                return false;
            }

            //if both event are index unaware
            if (OldStartingIndex < 0 && pendingEvent.OldStartingIndex < 0)
            {
                DiscardOriginalEventArgs();
                OldItems.AddRange(pendingEvent.OldItems);
                return true;
            }

            //if old item(s) should be inserted just before current items
            if (OldStartingIndex == pendingEvent.OldStartingIndex + pendingEvent.OldItems.Count)
            {
                DiscardOriginalEventArgs();
                OldItems.InsertRange(0, pendingEvent.OldItems);
                m_oldStartingIndex = pendingEvent.OldStartingIndex;
                return true;
            }

            //if old item(s) should be inserted after current
            if (OldStartingIndex == pendingEvent.OldStartingIndex)
            {
                DiscardOriginalEventArgs();
                OldItems.AddRange(pendingEvent.OldItems);
                return true;
            }

            //all other combinations can not be merged
            return false;
        }

        

    }
}
